package com.guosen.zebra.framework.starter.sso.utils;

import com.guosen.zebra.framework.starter.sso.constant.SsoConstant;
import com.guosen.zebra.framework.starter.sso.dto.EacResponse;
import com.guosen.zebra.framework.starter.sso.exception.SsoLoginFailException;
import com.guosen.zebra.framework.starter.sso.model.IasRequest;
import com.guosen.zebra.framework.starter.sso.properties.SsoProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.function.ServerRequest;

import java.time.LocalDateTime;

@Slf4j
public abstract class EACValidateUtils {

    private EACValidateUtils(){}

    /**
     * 验证sso返回参数是否正确
     */
    public static boolean validate(EacResponse eacResponse, SsoProperties ssoProperties) {
        return validateResult(eacResponse)
                && validateIasId(eacResponse, ssoProperties.getIasId())
                && validateTimeStamp(eacResponse, ssoProperties.getAuthExpiration())
                && validateAuthenticator(eacResponse, ssoProperties.getIasKey());
    }

    /**
     * 提取SSO系统的验证结果
     */
    public static EacResponse toEacResponse(ServerRequest request) {
        String iaSid = request.param(SsoConstant.IA_SID).orElse("");
        String userAccount = request.param(SsoConstant.USER_ACCOUNT).orElse("");
        String result = request.param(SsoConstant.RESULT).orElse("");
        String errorDescription = request.param(SsoConstant.ERROR_DESCRIPTION).orElse("");
        String authenticator = request.param(SsoConstant.AUTHENTICATOR).orElse("");
        String timeStamp = request.param(SsoConstant.TIME_STAMP).orElse("");

        EacResponse eacResponse = new EacResponse();
        eacResponse.setIasId(iaSid);
        eacResponse.setUserAccount(userAccount);
        eacResponse.setTimeStamp(timeStamp);
        eacResponse.setAuthenticator(authenticator);
        eacResponse.setResult(result);
        eacResponse.setErrorDescription(errorDescription);
        return eacResponse;
    }

    /**
     * 生成authenticator
     */
    public static String generateAuthenticator(IasRequest iasRequest) throws Exception {
        String originalAuthenticator = iasRequest.getIasId() + iasRequest.getTimeStamp() + iasRequest.getReturnUrl();
        String authenticatorDigest = PSOCryptography.GenerateDigest(originalAuthenticator);
        String strTobeDeCrypt = authenticatorDigest + originalAuthenticator;
        return PSOCryptography.Encrypt(strTobeDeCrypt, iasRequest.getIasKey(), PSOCryptography.defaultIV);
    }

    /**
     * 检查必传参数
     */
    public static void checkRequiredParams(EacResponse eacResponse) {
        assertNotEmpty("iasId", eacResponse.getIasId());
        assertNotEmpty("timeStamp", eacResponse.getTimeStamp());
        assertNotEmpty("userAccount", eacResponse.getUserAccount());
        assertNotEmpty("authenticator", eacResponse.getAuthenticator());
    }

    private static void assertNotEmpty(String field, String val) {
        if (StringUtils.isEmpty(val)) {
            String errorMsg = String.format("requestParams field %s is empty", field);
            log.error(errorMsg);
            throw new SsoLoginFailException(errorMsg);
        }
    }

    private static boolean validateResult(EacResponse eacResponse) {
        if (!SsoConstant.SUCCESS.equals(eacResponse.getResult())) {
            appendErrorDescription(eacResponse, "EAC return invalid result!");
            return false;
        }
        return true;
    }

    private static boolean validateTimeStamp(EacResponse eacResponse, Integer authExpiration) {
        // 时间戳校验（仅配置规定的认证期内有效，默认15分钟）
        LocalDateTime date = TimeUtils.addMinuteForDate(-authExpiration);
        LocalDateTime returnDate = TimeUtils.strToDateTime(eacResponse.getTimeStamp());
        if (returnDate.isBefore(date)) {
            appendErrorDescription(eacResponse, "sso authentication has expired!");
            return false;
        }
        return true;
    }

    /**
     * 验证sso返回的iasId与配置iasId是否相同
     */
    private static boolean validateIasId(EacResponse eacResponse, String configIasId) {
        if (!StringUtils.equals(configIasId, eacResponse.getIasId())) {
            appendErrorDescription(eacResponse, "inconsistent iasId!");
            return false;
        }
        return true;
    }

    private static boolean validateAuthenticator(EacResponse eacResponse, String configIasKey) {
        String authenticator = eacResponse.getAuthenticator();
        String originalAuthenticator = eacResponse.getIasId()
                + eacResponse.getTimeStamp()
                + eacResponse.getUserAccount()
                + eacResponse.getResult()
                + eacResponse.getErrorDescription();
        String authenticatorDigest = PSOCryptography.GenerateDigest(originalAuthenticator);
        String strTobeDecrypted = authenticatorDigest + originalAuthenticator;
        try {
            String encryptCurrentAuthenticator = PSOCryptography.Encrypt(
                    strTobeDecrypted, configIasKey,
                    PSOCryptography.defaultIV);
            return StringUtils.equals(authenticator, encryptCurrentAuthenticator);
        } catch (Exception e) {
            appendErrorDescription(eacResponse, "Fail to encrypt authenticator, message: " + e.getMessage());
            log.error("Fail to encrypt authenticator, message: {}", e.getMessage(), e);
            return false;
        }
    }

    private static void appendErrorDescription(EacResponse eacResponse, String errorMsg) {
        eacResponse.setErrorDescription(eacResponse.getErrorDescription() + "\n" + errorMsg);
    }
}
